/**
 * Copyright 2013 yangming.liu<liuyangming@gmail.com>.
 *
 * This copyrighted material is made available to anyone wishing to use, modify,
 * copy, or redistribute it subject to the terms and conditions of the GNU
 * Lesser General Public License, as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License
 * for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this distribution; if not, see <http://www.gnu.org/licenses/>.
 */
package org.bytesoft.openjta.recovery;

import java.util.Iterator;
import java.util.List;
import java.util.Set;

import javax.transaction.HeuristicMixedException;
import javax.transaction.HeuristicRollbackException;
import javax.transaction.RollbackException;
import javax.transaction.SystemException;
import javax.transaction.TransactionManager;

import org.bytesoft.openjta.TransactionConstants;
import org.bytesoft.openjta.TransactionContext;
import org.bytesoft.openjta.TransactionImpl;
import org.bytesoft.openjta.TransactionManagerImpl;
import org.bytesoft.openjta.TransactionRepository;
import org.bytesoft.openjta.archive.TransactionArchive;
import org.bytesoft.openjta.archive.XAResourceArchive;
import org.bytesoft.openjta.resource.LocalTerminator;
import org.bytesoft.openjta.supports.TransactionLogger;
import org.bytesoft.openjta.xa.XidImpl;

public class RecoveryManager {
	private TransactionManager transactionManager;

	public void reconstruct() {
		TransactionRepository repository = this.getTransactionRepository();
		TransactionLogger transactionLogger = repository.getTransactionLogger();
		Set<TransactionArchive> transactions = transactionLogger.getRecoveryArchives();
		Iterator<TransactionArchive> itr = transactions.iterator();
		while (itr.hasNext()) {
			TransactionArchive meta = itr.next();
			TransactionImpl transaction = this.reconstructTransaction(meta);
			XidImpl globalXid = transaction.getGlobalXid();
			repository.putTransaction(globalXid, transaction);
			repository.putErrorTransaction(globalXid, transaction);
		}
	}

	public RecoveredTransaction reconstructTransaction(TransactionArchive archive) {
		int transactionMode = archive.getTransactionMode();

		RecoveredTransaction transaction = null;
		if (transactionMode == TransactionConstants.TX_MODE_NONE) {
			transaction = new RegularRecoveredTransactionImpl();
		} else if (transactionMode == TransactionConstants.TX_MODE_LAO) {
			transaction = new LAORecoveredTransactionImpl();
		} else if (transactionMode == TransactionConstants.TX_MODE_LRO) {
			transaction = new LRORecoveredTransactionImpl();
		} else if (transactionMode == TransactionConstants.TX_MODE_OPC) {
			throw new IllegalStateException();
		} else {
			throw new IllegalStateException();
		}
		TransactionManagerImpl txManager = this.getTransactionManagerImpl();
		TransactionRepository repository = this.getTransactionRepository();
		TransactionLogger transactionLogger = repository.getTransactionLogger();
		transaction.setTransactionManager(txManager);
		transaction.setTransactionLogger(transactionLogger);

		transaction.clone(archive);
		LocalTerminator localTerminator = transaction.getLocalTerminator();
		localTerminator.setTransactionLogger(transactionLogger);
		List<XAResourceArchive> archives = transaction.getXAResourceArchives();
		localTerminator.getAllXAResourceArchives().addAll(archives);
		localTerminator.getDelistedXAResourceArchives().addAll(archives);

		try {
			transaction.regulateTransaction();
			transaction.setRecoveryReady(true);
		} catch (IllegalStateException ex) {
			transaction.setRecoveryReady(false);
		} catch (RuntimeException ex) {
			transaction.setRecoveryReady(false);
		}

		return transaction;
	}

	public void recover() {
		TransactionRepository repository = this.getTransactionRepository();
		Iterator<TransactionImpl> itr = repository.getErrorTransactionSet().iterator();
		while (itr.hasNext()) {
			TransactionImpl transaction = itr.next();
			try {
				this.recoverTransaction(transaction);
			} catch (HeuristicMixedException ex) {
				ex.printStackTrace();
			} catch (SystemException ex) {
				ex.printStackTrace();
			} catch (RuntimeException ex) {
				ex.printStackTrace();
			}
		}
	}

	public void recoverTransaction(XidImpl globalXid) throws HeuristicMixedException, SystemException {
		TransactionRepository repository = this.getTransactionRepository();
		TransactionImpl transaction = repository.getErrorTransaction(globalXid);
		this.recoverTransaction(transaction);
	}

	public void recoverTransaction(TransactionImpl transaction) throws HeuristicMixedException, SystemException {
		TransactionContext transactionContext = transaction.getTransactionContext();
		if (transactionContext.isCoordinator() || transaction.isCoordinateTaken()) {
			if (RecoveredTransaction.class.isInstance(transaction)) {
				this.recoverRecoveredTransaction((RecoveredTransaction) transaction);
			} else {
				this.recoverFreshTransaction(transaction);
			}
		}
	}

	public void recoverFreshTransaction(TransactionImpl transaction) throws HeuristicMixedException, SystemException {
		// TODO
	}

	public void recoverRecoveredTransaction(RecoveredTransaction transaction) throws HeuristicMixedException,
			SystemException {
		if (transaction.isRecoveryRollbackRequired()) {
			transaction.processRollback();
			transaction.cleanup();
		} else {
			try {
				transaction.processCommit();
				transaction.cleanup();
			} catch (SecurityException ex) {
				SystemException exp = new SystemException();
				exp.initCause(exp);
				throw exp;
			} catch (HeuristicRollbackException ex) {
				// ignore
			} catch (RollbackException ex) {
				// ignore
			} catch (RuntimeException ex) {
				SystemException exp = new SystemException();
				exp.initCause(exp);
				throw exp;
			}
		}
	}

	public TransactionRepository getTransactionRepository() {
		TransactionManagerImpl txm = (TransactionManagerImpl) this.transactionManager;
		return txm.getTransactionRepository();
	}

	public TransactionManagerImpl getTransactionManagerImpl() {
		return (TransactionManagerImpl) transactionManager;
	}

	public void setTransactionManager(TransactionManager transactionManager) {
		this.transactionManager = transactionManager;
	}

}
